#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import pam
import base64
import uvicorn
import binascii
import starlette
import starlette.routing
import starlette.responses
import starlette.exceptions
import starlette.middleware
import starlette.middleware.base
import starlette.staticfiles
import starlette.applications
from mc_util import McUtil
from mc_util import StarletteHdrs
from mc_param import McConst
from mc_updater import McMirrorSiteUpdateStatus


class McMainAdvertiser:

    def __init__(self, param):
        self._param = param

        self._app = None
        self._server = None
        self._serverTask = None
        self._param.mainloop.run_until_complete(self._start())

    def dispose(self):
        self._param.mainloop.run_until_complete(self._stop())

    async def _start(self):
        try:
            self._app = starlette.applications.Starlette(routes=[
                starlette.routing.Mount('/api', app=SubAppApi(self._param)),
                starlette.routing.Mount('/', app=SubAppWeb(self._param)),
            ])

            # maxBytes=McConst.rotateLogFileSize,
            # backupCount=McConst.rotateLogFileCount))

            uvLogConfig = uvicorn.config.LOGGING_CONFIG.copy()
            uvLogConfig["handlers"]["default"]["class"] = "logging.handlers.RotatingFileHandler"
            uvLogConfig["handlers"]["default"]["filename"] = os.path.join(McConst.logDir, 'main-httpd.log')
            del uvLogConfig["handlers"]["default"]["stream"]
            uvLogConfig["handlers"]["access"]["class"] = "logging.handlers.RotatingFileHandler"
            uvLogConfig["handlers"]["access"]["filename"] = os.path.join(McConst.logDir, 'main-httpd.log')
            del uvLogConfig["handlers"]["access"]["stream"]
            uvConfig = uvicorn.config.Config(self._app, host=self._param.listenIp, port=self._param.mainPort, log_config=uvLogConfig)
            uvConfig.load()

            self._server = uvicorn.server.Server(config=uvConfig)
            self._server.lifespan = uvConfig.lifespan_class(uvConfig)
            await self._server.startup()
            assert not self._server.should_exit
            self._serverTask = McUtil.asyncSafeTaskCreate(self._param.mainloop, self._server.main_loop(), bNoCancel=True, bTerminateOnError=True, bTerminateOnEnd=True)
        except Exception:
            await self._stop()
            raise

    async def _stop(self):
        if self._serverTask is not None:
            McUtil.asyncSafeTaskSetTerminateOnEnd(self._serverTask, False)
            McUtil.asyncSafeTaskSetTerminateOnError(self._serverTask, False)
            self._server.should_exit = True
            await self._serverTask
            self._serverTask = None
        if self._server is not None:
            await self._server.shutdown()
            self._server = None
        if self._app is not None:
            # no dispose needed for self._app
            self._app = None


class SubAppApi(starlette.applications.Starlette):

    def __init__(self, param):
        self._param = param
        super().__init__(routes=[
            starlette.routing.Route('/mirrors', self.mirrors, methods=[StarletteHdrs.METH_GET]),
        ])

    async def mirrors(self, request):
        data = dict()
        for msId, msObj in self._param.mirrorSiteDict.items():
            if await self._param.updater.isMirrorSiteInitialized(msId):
                data[msId] = {
                    "last-update-time": "",
                    "access": {k: self._param.advertiserDict[k].get_access_info(msId) for k in msObj.advertiserDict},
                }
        return starlette.responses.JSONResponse(data)


class SubAppWeb(starlette.applications.Starlette):

    def __init__(self, param):
        self._param = param
        super().__init__(routes=[
            starlette.routing.Route('/web/mirror-site', self.listMirrorSites, methods=[StarletteHdrs.METH_GET]),
            starlette.routing.Route('/web/mirror-site/{id}', self.getMirrorSite, methods=[StarletteHdrs.METH_GET]),
            starlette.routing.Route('/web/mirror-site/{id}/update-now', self._webOneMirrorUpdateNow, methods=[StarletteHdrs.METH_POST]),
            starlette.routing.Mount('/', app=starlette.staticfiles.StaticFiles(directory=McConst.dataDir, html=True)),
        ], middleware=[
            starlette.middleware.Middleware(MiddleWareAuth),
        ])

    async def listMirrorSites(self, request):
    #     offset = int(request.query_params.get("offset", 0))
    #     limit = int(request.query_params.get("limit", None))
    # # Set the number of articles per page
    # per_page = 10
    # # Calculate the start and end indices of the articles for the current page
    # start = (page - 1) * per_page
    # end = start + per_page
    # # Get the articles for the current page
    # page_articles = articles[start:end]
    # # Check if there is a next page
    # has_next = end < len(articles)
    # # Check if there is a previous page
    # has_prev = page > 1

    # response_data = {
    #     "articles": page_articles,
    #     "has_next": has_next,
    #     "has_prev": has_prev,
    #     "page": page,
    #     "per_page": per_page
    # }
    # return JSONResponse(response_data)

        data = dict()
        for msId, msObj in self._param.mirrorSiteDict.items():
            data[msId] = {
                "last-update-time": "",
                "access": {k: self._param.advertiserDict[k].get_access_info(msId) for k in msObj.advertiserDict},
            }
        return starlette.responses.JSONResponse(data)

    async def getMirrorSite(self, request):
        msId = request.path_params["id"]
        if msId not in self._param.mirrorSiteDict:
            raise starlette.exceptions.HTTPException(status_code=404, detail="mirror site not found")
        return starlette.responses.JSONResponse(None)

    async def _webOneMirrorUpdateNow(self, request):
        msId = request.path_params["id"]
        if msId not in self._param.mirrorSiteDict:
            raise starlette.exceptions.HTTPException(status_code=404, detail="mirror site not found")

        s = await self._param.updater.getMirrorSiteUpdateStatus(msId)
        if s in [McMirrorSiteUpdateStatus.INIT, McMirrorSiteUpdateStatus.INITING, McMirrorSiteUpdateStatus.INIT_FAIL]:
            raise starlette.exceptions.HTTPException(status_code=400, detail="mirror site has not been initialized")
        if s in [McMirrorSiteUpdateStatus.UPDATING, McMirrorSiteUpdateStatus.MAINTAINING]:
            raise starlette.exceptions.HTTPException(status_code=400, detail="mirror site is updating")

        await self._param.updater.cancelSleep(msId)
        return starlette.responses.JSONResponse(None)


class MiddleWareAuth(starlette.middleware.base.BaseHTTPMiddleware):

    async def dispatch(self, request, call_next):
        # if request.path.startswith('/api'):
        #     return await handler(request)

        while StarletteHdrs.AUTHORIZATION in request.headers:
            try:
                authType, encodedCredentials = request.headers[StarletteHdrs.AUTHORIZATION].split(" ", 1)
            except ValueError:
                break

            if authType.lower() != "basic":
                break

            try:
                decoded = base64.b64decode(encodedCredentials.encode("ascii"), validate=True).decode()
            except binascii.Error:
                break

            try:
                # RFC 2617 HTTP Authentication
                # https://www.ietf.org/rfc/rfc2617.txt
                # the colon must be present, but the username and password may be otherwise blank.
                username, password = decoded.split(":", 1)
            except ValueError:
                break

            if not pam.authenticate(username, password, service="mirrors"):
                break

            return await call_next(request)

        return starlette.responses.Response(
            'UNAUTHORIZED',
            status_code=401,
            headers={
                StarletteHdrs.WWW_AUTHENTICATE: 'Basic realm="mirrors"',
                StarletteHdrs.CONTENT_TYPE: 'text/html; charset=utf-8',
            },
        )





# async def _indexHandler(self, request):
#     data = {
#         "static": {
#             "title": "mirror site",
#             "name": "镜像名",
#             "last_update_time": "上次更新时间",
#             "next_update_time": "",
#             "help": "使用帮助",
#         },
#         "mirror_site_dict": self.__getMirrorSiteDict(),
#     }
#     return templates.TemplateResponse('index.jinja2', {"request": request, "data": data})

# async def __getMirrorSiteDict(self):
#     ret = dict()
#     for msId, msObj in self._param.mirrorSiteDict.items():
#         if await self._param.updater.isMirrorSiteInitialized(msId):
#             ret[msId] = {
#                 "last-update-time": "",
#                 "access": {k: self._param.advertiserDict[k].get_access_info(msId) for k in msObj.advertiserDict},
#             }
#     return ret
